import {LngLat} from "./LngLat";

export class LngLatBounds {
  _ne: LngLat;
  _sw: LngLat;

  /**
   * Set the northeast corner of the bounding box
   *
   * @param {LngLat} ne
   * @returns {LngLatBounds} `this`
   */
  setNorthEast(ne: LngLat): LngLatBounds {
    this._ne = new LngLat(ne.lng, ne.lat);
    return this;
  }

  /**
   * Set the southwest corner of the bounding box
   *
   * @param {LngLat} sw
   * @returns {LngLatBounds} `this`
   */
  setSouthWest(sw: LngLat): LngLatBounds {
    this._sw = new LngLat(sw.lng, sw.lat);
    return this;
  }

  /**
   * Extend the bounds to include a given LngLat or LngLatBounds.
   *
   * @param {LngLat|LngLatBounds} obj object to extend to
   * @returns {LngLatBounds} `this`
   */
  extend(obj: LngLat|LngLatBounds): LngLatBounds {
    const sw = this._sw,
        ne = this._ne;
    let sw2, ne2;

    if (obj instanceof LngLat) {
      sw2 = obj;
      ne2 = obj;
    } else if (obj instanceof LngLatBounds) {
      sw2 = obj._sw;
      ne2 = obj._ne;

      if (!sw2 || !ne2) return this;
    } else {
      return this;
    }

    if (!sw && !ne) {
      this._sw = new LngLat(sw2.lng, sw2.lat);
      this._ne = new LngLat(ne2.lng, ne2.lat);
    } else {
      sw.lng = Math.min(sw2.lng, sw.lng);
      sw.lat = Math.min(sw2.lat, sw.lat);
      ne.lng = Math.max(ne2.lng, ne.lng);
      ne.lat = Math.max(ne2.lat, ne.lat);
    }

    return this;
  }

  /**
   * Returns the geographical coordinate equidistant from the bounding box's corners.
   *
   * @returns {LngLat} The bounding box's center.
   * @example
   * var llb = new mapboxgl.LngLatBounds([-73.9876, 40.7661], [-73.9397, 40.8002]);
   * llb.getCenter(); // = LngLat {lng: -73.96365, lat: 40.78315}
   */
  getCenter(): LngLat {
    return new LngLat((this._sw.lng + this._ne.lng) / 2, (this._sw.lat + this._ne.lat) / 2);
  }

  /**
   * Returns the southwest corner of the bounding box.
   *
   * @returns {LngLat} The southwest corner of the bounding box.
   */
  getSouthWest(): LngLat { return this._sw; }

  /**
  * Returns the northeast corner of the bounding box.
  *
  * @returns {LngLat} The northeast corner of the bounding box.
   */
  getNorthEast(): LngLat { return this._ne; }

  /**
  * Returns the northwest corner of the bounding box.
  *
  * @returns {LngLat} The northwest corner of the bounding box.
   */
  getNorthWest(): LngLat { return new LngLat(this.getWest(), this.getNorth()); }

  /**
  * Returns the southeast corner of the bounding box.
  *
  * @returns {LngLat} The southeast corner of the bounding box.
   */
  getSouthEast(): LngLat { return new LngLat(this.getEast(), this.getSouth()); }

  /**
  * Returns the west edge of the bounding box.
  *
  * @returns {number} The west edge of the bounding box.
   */
  getWest(): number { return this._sw.lng; }

  /**
  * Returns the south edge of the bounding box.
  *
  * @returns {number} The south edge of the bounding box.
   */
  getSouth(): number { return this._sw.lat; }

  /**
  * Returns the east edge of the bounding box.
  *
  * @returns {number} The east edge of the bounding box.
   */
  getEast(): number { return this._ne.lng; }

  /**
  * Returns the north edge of the bounding box.
  *
  * @returns {number} The north edge of the bounding box.
   */
  getNorth(): number { return this._ne.lat; }

  /**
   * Returns the bounding box represented as an array.
   *
   * @returns {Array<Array<number>>} The bounding box represented as an array, consisting of the
   *   southwest and northeast coordinates of the bounding represented as arrays of numbers.
   * @example
   * var llb = new mapboxgl.LngLatBounds([-73.9876, 40.7661], [-73.9397, 40.8002]);
   * llb.toArray(); // = [[-73.9876, 40.7661], [-73.9397, 40.8002]]
   */
  toArray() {
    return [this._sw.toArray(), this._ne.toArray()];
  }

  /**
   * Return the bounding box represented as a string.
   *
   * @returns {string} The bounding box represents as a string of the format
   *   `'LngLatBounds(LngLat(lng, lat), LngLat(lng, lat))'`.
   * @example
   * var llb = new mapboxgl.LngLatBounds([-73.9876, 40.7661], [-73.9397, 40.8002]);
   * llb.toString(); // = "LngLatBounds(LngLat(-73.9876, 40.7661), LngLat(-73.9397, 40.8002))"
   */
  toString() {
    return `LngLatBounds(${this._sw.toString()}, ${this._ne.toString()})`;
  }

  /**
   * Returns a `LngLatBounds` from the coordinates extended by a given `radius`.
   * var ll = new mapboxgl.LngLat(-73.9749, 40.7736);
   * ll.toBounds(100).toArray(); // = [[-73.97501862141328, 40.77351016847229], [-73.97478137858673, 40.77368983152771]]
   */
  static fromLngLatAndRadius(point: LngLat, radius: number) {
    const earthCircumferenceInMetersAtEquator = 40075017;
    const latAccuracy = 360 * radius / earthCircumferenceInMetersAtEquator,
        lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * point.lat);

    const LngLatBounds = require("./lng_lat_bounds");
    return new LngLatBounds(new LngLat(point.lng - lngAccuracy, point.lat - latAccuracy),
        new LngLat(point.lng + lngAccuracy, point.lat + latAccuracy));
  }
}